package com.qiaoba.auth.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.qiaoba.api.auth.constant.SecurityConstant;
import com.qiaoba.api.auth.entity.dto.LoginDTO;
import com.qiaoba.api.auth.service.AuthConfigApiService;
import com.qiaoba.api.auth.service.SysUserDetailsApiService;
import com.qiaoba.api.auth.utils.JwtUtil;
import com.qiaoba.api.auth.utils.SecurityUtil;
import com.qiaoba.api.system.entity.vo.RouterVO;
import com.qiaoba.api.system.entity.vo.SysMenuVO;
import com.qiaoba.api.system.entity.vo.SysUserVO;
import com.qiaoba.api.system.service.SysMenuApiService;
import com.qiaoba.api.system.service.SysUserApiService;
import com.qiaoba.auth.constant.OnlineUserConstant;
import com.qiaoba.auth.service.OnlineUserService;
import com.qiaoba.auth.service.SysLoginService;
import com.qiaoba.common.base.constant.BaseConstant;
import com.qiaoba.common.base.exception.ServiceException;
import com.qiaoba.nosql.redis.service.RedisService;
import com.qiaoba.web.util.IpUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 登录 服务层实现
 *
 * @author ailanyin
 * @date 2023-09-24 13:26:01
 */
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {

    private final RedisService redisService;
    private final HttpServletRequest request;
    private final SysUserDetailsApiService userDetailsService;
    private final SysUserApiService sysUserApiService;
    private final OnlineUserService onlineUserService;
    private final AuthConfigApiService authConfigApiService;
    private final SysMenuApiService sysMenuApiService;

    @Override
    public Map<String, Object> getCaptchaImage() {
        Map<String, Object> map = new HashMap<String, Object>(4);

        map.put("register", authConfigApiService.getRegisterConfig());
        if (!authConfigApiService.getCaptchaConfig()) {
            map.put("captchaEnabled", false);
            return map;
        }

        String uuid = UUID.randomUUID().toString(true);
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(150, 50, 4, 0);
        // 自定义验证码内容为四则运算方式
        captcha.setGenerator(new MathGenerator(1));
        // 重新生成code
        captcha.createCode();
        map.put("uuid", uuid);
        map.put("img", captcha.getImageBase64());
        redisService.set(SecurityConstant.CAPTCHA_KEY + uuid, captcha.getCode(), 120);

        return map;
    }

    /**
     * 登陆核心方法
     *
     * @param dto dto
     * @return token
     * @see com.qiaoba.auth.aspect.LoginLogAspect 登陆日志AOP
     */
    @Override
    public String login(LoginDTO dto) {
        Boolean blacklistSwitch = authConfigApiService.getBlacklistConfig();
        // 校验黑名单
        validateBlacklist(blacklistSwitch);
        // 校验验证码
        authConfigApiService.validateCaptcha(dto.getCode(), dto.getUuid());
        // username查询用户信息
        SysUserVO sysUser = sysUserApiService.selectVoByUsername(dto.getUsername(), true);
        // 检查账号信息
        validateUser(dto.getUsername(), sysUser);
        // 检验密码
        validatePassword(blacklistSwitch, dto.getUsername(), sysUser.getPassword(), dto.getPassword());
        // 缓存在线用户
        String deviceSn = cacheOnlineUser(dto.getUsername(), sysUser.getNickname());
        // 缓存userDetails
        userDetailsService.toCache(sysUser.getUsername(), deviceSn);
        // 更新用户最后登陆IP和登陆时间
        sysUserApiService.updateLastLoginInfo(sysUser.getUserId(), IpUtil.getIp(request), new Date());
        // 生成Token
        String token = JwtUtil.generateToken(sysUser.getUserId(), sysUser.getUsername(), deviceSn, sysUser.getNickname());
        // 缓存Token
        redisService.set(SecurityConstant.LOGIN_USER_TOKENS_REDIS_KEY + deviceSn, token, authConfigApiService.getTokenExpireTime());
        return deviceSn;
    }

    @Override
    public List<RouterVO> selectRoutersByUserId(String userId) {
        List<SysMenuVO> sysMenuList = sysMenuApiService.selectByUserId(userId);
        List<RouterVO> routerList = sysMenuApiService.menusToRouters(sysMenuList);
        return routerList;
    }

    private void validateBlacklist(Boolean blacklistSwitch) {
        if (blacklistSwitch) {
            String ip = IpUtil.getIp(request);
            if (redisService.hasKey(SecurityConstant.BLACKLIST_KEY + ip)) {
                throw new ServiceException(SecurityConstant.HAS_BEEN_PULLED_BLACK);
            }
        }
    }

    private void validatePassword(Boolean blacklistSwitch, String username, String password, String inputPassword) {
        boolean result = SecurityUtil.matchesPassword(inputPassword, password);
        // 密码正确
        if (result) {
            if (blacklistSwitch) {
                // 删除错误次数
                String ip = IpUtil.getIp(request);
                redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
            }

        } else {
            // 密码错误
            String msg = beforePasswordError(blacklistSwitch, username);
            throw new ServiceException(msg);
        }
    }

    private String beforePasswordError(Boolean blacklistSwitch, String username) {
        // 未开启->直接结束
        if (!blacklistSwitch) {
            return "密码错误";
        }
        // 开启->继续
        // 错误次数是否到达允许最大错误次数
        String ip = IpUtil.getIp(request);
        Integer maxAllowCount = authConfigApiService.getAllowMaxErrorCount();
        Integer ipErrorCount = getIpErrorCount(ip);
        if (ipErrorCount >= maxAllowCount) {
            // 是-> 进入黑名单库 && 返回"IP已被拉黑"
            redisService.set(SecurityConstant.BLACKLIST_KEY + ip, username, 60 * authConfigApiService.getBlacklistExpireTime());
            redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
            return SecurityConstant.HAS_BEEN_PULLED_BLACK;
        } else {
            //  否-> 错误次数+1 && 返回"你还剩xx次错误机会"
            ipErrorCount++;
            redisService.set(SecurityConstant.LOGIN_ERROR_COUNT + ip, ipErrorCount, 600);
            return StrUtil.format("密码错误, 还有[{}]次错误机会", (maxAllowCount - ipErrorCount));
        }

    }

    private Integer getIpErrorCount(String ip) {
        Object ipErrorCount = redisService.get(SecurityConstant.LOGIN_ERROR_COUNT + ip);
        return Objects.isNull(ipErrorCount) ? 0 : Integer.parseInt(ipErrorCount.toString());
    }

    private void validateUser(String username, SysUserVO user) {
        if (ObjectUtil.isNull(user)) {
            throw new ServiceException(StrUtil.format("登录用户：{} 不存在", username));
        } else if (BaseConstant.ABNORMAL.equals(user.getStatus())) {
            throw new ServiceException(StrUtil.format("对不起, 您的账号：{} 已被禁用", username));
        }
    }


    private String cacheOnlineUser(String username, String nickname) {
        // 不允许多设备同时登陆
        if (!authConfigApiService.checkAllowBothOnline()) {
            String oldDeviceSn = redisService.getObject(OnlineUserConstant.LAST_LOGIN_USER_REDIS_KEY + username, String.class);
            // 删除旧用户的在线信息
            onlineUserService.deleteOnlineInfo(oldDeviceSn);
            // 删除旧用户的token
            redisService.del(SecurityConstant.LOGIN_USER_TOKENS_REDIS_KEY + oldDeviceSn);
        }
        String deviceSn = UUID.fastUUID().toString(true);
        onlineUserService.insert(request, deviceSn, username, nickname);
        redisService.set(OnlineUserConstant.LAST_LOGIN_USER_REDIS_KEY + username, deviceSn, authConfigApiService.getTokenExpireTime());
        return deviceSn;
    }

}
